package io.xxx.mario.task.talk;

import io.xxx.mario.robot.Robot;
import io.xxx.mario.robot.RobotMapper;
import io.xxx.mario.task.Task;
import io.xxx.mario.task.TaskMessage;
import io.xxx.mario.task.receive.Receiver;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;

@Slf4j
@Component
public class TalkerManager implements ApplicationRunner, Talker {

    /**
     * key: talkerType:robotId
     * value: Talker impl
     */
    public static final Map<String, GenericTalker> TALKER_CACHE = new ConcurrentHashMap<>();

    @Resource
    private RobotMapper robotMapper;

    @Resource
    private TalkerFactory talkerFactory;

    @Resource
    private CuratorFramework curator;

    @Value("${robot.talker.path:/robot/talkers}")
    private String rootPath;

    private final ThreadLocalRandom random = ThreadLocalRandom.current();

    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<Robot> robots = robotMapper.selectList(null);
        for (Robot robot : robots) {
            Map<String, GenericTalker> genericTalkers = getGenericTalker(robot);
            TALKER_CACHE.putAll(genericTalkers);
        }
        for (Map.Entry<String, GenericTalker> entry : TALKER_CACHE.entrySet()) {
            String talkerKey = entry.getKey();
            GenericTalker talker = entry.getValue();
            String path = rootPath + "/" + talkerKey;
            Stat stat = curator.checkExists().forPath(path);
            if (stat == null) {
                curator.create().forPath(path);
            }
            Thread.startVirtualThread(talker).start();
        }
        List<String> children = curator.getChildren().forPath(rootPath);
        for (String child : children) {
            if (!TALKER_CACHE.containsKey(child)) { // zk存在，db不存在，需要删除节点
                curator.delete().forPath(rootPath + child);
            }
        }

        // TODO 注册rootPath的监听事件
        curator.getCuratorListenable()
                .addListener((client, event) -> System.out.println(event));
    }

    private Map<String, GenericTalker> getGenericTalker(Robot robot) {
        String ipadTalkerKey = getCacheKey(TalkerType.IPAD, robot);
        GenericTalker ipadTalker = talkerFactory.getTalker(TalkerType.IPAD, robot);
        String appTalkerKey = getCacheKey(TalkerType.APP, robot);
        GenericTalker appTalker = talkerFactory.getTalker(TalkerType.APP, robot);
        return new HashMap<>() {{
            put(ipadTalkerKey, ipadTalker);
            put(appTalkerKey, appTalker);
        }};
    }

    @Override
    public void say(Task task, Receiver receiver, List<TaskMessage> messages) throws InterruptedException {
        Long robotId = receiver.robotId();
        Robot robot = robotMapper.selectById(robotId);
        TalkerType talkerType = getTalkerType(task);
        Talker talker = TALKER_CACHE.get(getCacheKey(talkerType, robot));
        talker.say(task, receiver, messages);
    }

    private TalkerType getTalkerType(Task task) {
        int ipadScale = task.getIpadScale();
        int appScale = task.getAppScale();

        if (ipadScale == 100) {
            return TalkerType.IPAD;
        }
        if (appScale == 100) {
            return TalkerType.APP;
        }
        int i = random.nextInt(1, 100);
        if (i > 0 && i <= ipadScale) {
            return TalkerType.IPAD;
        }
        if (i > ipadScale && i <= appScale) {
            return TalkerType.APP;
        }
        return TalkerType.IPAD;
    }

    private String getCacheKey(TalkerType talkerType, Robot robot) {
        return talkerType.name() + ":" + robot.getId();
    }
}
